昨天介紹了 陣列 和 矩陣,今天來介紹也很常見的 鏈結串列 (Linked List)。
Linked List 也有分成幾種:
Linked List 大概長下面這樣:
每一個 Data 區塊 + Next 區塊 稱為一個 Node (節點) 作用如下:
而最前面的 Head 會指向第一個 Node,但是 Head 不是一個 Node,所以不會有資料
而最後面的 Tail 會指向最後一個 Node,但是 Tail 不是一個 Node,所以不會有資料
首先可以先定義 Node
class Node:
    def __init__(self, data):
        self.data = data  # 資料
        self.next = None  # 指向下一個節點
再來就可以建立 Linked List 的資料結構
class LinkedList:
    def __init__(self):
        self.head = None # Head 存放第一個 Node 的位置
        self.tail = None # Tail 存放最後一個 Node 的位置
但目前這個 Linked List 裡面並沒有任何的 Node 所以我們 需要加入 (append)
    # 這裡的 append 跟 python list 一樣都是從最後面加入
    def append(self, data):
        new_node = Node(data)
        if not self.head:  # 如果 Liked List 為空,將 Head、Tail 指向新 Node
            self.head = new_node
            self.tail = new_node
            return
        self.tail.next = new_node  # 如果非空就直接用 tail 連接
        self.tail = new_node       # 更新 tail
現在想要從最前面加入 (prepend) 則需要
    def prepend(self, data):
        new_node = Node(data)
        new_node.next = self.head # 先把 Head 加入 next
        self.head = new_node      # 再把新 Node 設為 Head
        if self.tail is None:  # 如果 Linked List 原本為空
            self.tail = new_node
如果要從中間某個節點後插入 (Insert)
    def insert(self, pivot_node, data):
        if pivot_node is None:
            raise ValueError("pivot_node 不能是 None")
        # 將新的 Node 加到選定的 Node 的後面
        new_node = Node(data)
        new_node.next = pivot_node.next
        pivot_node.next = new_node
        if self.tail == pivot_node: # 如果插入在尾端,要更新 tail
            self.tail = new_node
再來想 刪除 (Delete) 的話
    def delete(self, node):
        if node is None: # 不能刪除空的
            return
        # 如果刪除的是 head
        if self.head == node:
            self.head = node.next
            if self.head is None:  # 如果刪完 Linked List 為空
                self.tail = None
            return
        # 尋找前一個節點,需要從頭開始
        prev = self.head
        while prev and prev.next != node:
            prev = prev.next
        if prev is None: # 沒找到該節點,可能 Linked List 有問題
            return
        prev.next = node.next
        if node == self.tail:  # 如果刪掉的是尾端
            self.tail = prev
            
        node.next = None # 清除與 Linked List 的關係
那這裡就可以發現,單向鏈結串列 在做 Delete 的時候需要從頭開始找前一個 Node,會需要 O(N),所以就出現了雙 向鏈結串列 (Doubly Linked List)
每個 Node 除了 Next,還多了一個 Prev,可以指回上一個節點,這樣刪除時就不用再從頭找前一個節點。

class DNode:
    def __init__(self, data):
        self.data = data
        self.next = None  # 指向下一個節點
        self.prev = None  # 指回上一個節點
class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
    def append(self, data):
        new_node = DNode(data)
        if not self.head:
            self.head = new_node
            self.tail = new_node
            return
        self.tail.next = new_node
        new_node.prev = self.tail  # 多一個 Prev
        self.tail = new_node
    def prepend(self, data):
        new_node = DNode(data)
        if not self.head:  
            self.head = new_node
            self.tail = new_node
            return
        new_node.next = self.head
        self.head.prev = new_node  # 舊 Head 的 Prev 指向新 Node
        self.head = new_node       # 再把新 Node 設成 Head
    def insert_after(self, pivot_node, data):
        if pivot_node is None:
            raise ValueError("pivot_node 不能是 None")
        new_node = DNode(data)
        new_node.next = pivot_node.next
        new_node.prev = pivot_node
        if pivot_node.next:  # 如果不是尾端
            pivot_node.next.prev = new_node
        pivot_node.next = new_node
        if self.tail == pivot_node:  # 如果插在尾端,要更新 tail
            self.tail = new_node
    def delete(self, node):  # 雙向的不需要從頭開始找,只要 O(1)
        if node is None:
            return
        if node == self.head:  # 如果刪的是頭
            self.head = node.next
            if self.head:  # 如果還有下一個
                self.head.prev = None
            else:  # 如果刪完空了
                self.tail = None
                
        elif node == self.tail:  # 如果刪的是尾
            self.tail = node.prev
            if self.tail:
                self.tail.next = None
                
        else:  # 中間的 node
            node.prev.next = node.next
            node.next.prev = node.prev
        node.next = None
        node.prev = None
循環鏈結串列 (Circular Linked List) 就是把頭尾連起來,有單向跟雙向
單向的就是最後一個 Node 的 Next 會指向第一個 Node,整個串列首尾相接。
雙向的就是多了 Prev,最後一個 Node 的 Prev 會指向第一個 Node
class CircularLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
    def append(self, data):
        new_node = Node(data)
        if not self.head:  # 如果空的
            self.head = new_node
            self.tail = new_node
            new_node.next = new_node  # 自己指自己
            return
        self.tail.next = new_node
        new_node.next = self.head  # 新 Node 指回 Head
        self.tail = new_node
    def prepend(self, data):
        new_node = Node(data)
        if not self.head:  # 如果空的
            self.head = new_node
            self.tail = new_node
            new_node.next = new_node
            return
        new_node.next = self.head
        self.head = new_node
        self.tail.next = self.head  # Tail 重新指回新的 Head
    def delete(self, data):
        if not self.head:
            return
        curr = self.head
        prev = self.tail
        while True:
            if curr.data == data:  # 找到要刪的節點
                if curr == self.head:  # 刪頭
                    self.head = curr.next
                    self.tail.next = self.head
                elif curr == self.tail:  # 刪尾
                    self.tail = prev
                    self.tail.next = self.head
                else:  # 中間
                    prev.next = curr.next
                return
            prev = curr
            curr = curr.next
            if curr == self.head:  # 整圈都沒找到
                break
class DoublyCircularLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
    def append(self, data):
        new_node = Node(data)
        if not self.head: 
            self.head = new_node
            self.tail = new_node
            new_node.next = new_node
            new_node.prev = new_node
            return
        
        new_node.prev = self.tail
        new_node.next = self.head
        self.tail.next = new_node
        self.head.prev = new_node
        self.tail = new_node
    def prepend(self, data):
        new_node = Node(data)
        if not self.head:  
            self.head = new_node
            self.tail = new_node
            new_node.next = new_node
            new_node.prev = new_node
            return
        
        new_node.next = self.head
        new_node.prev = self.tail
        self.head.prev = new_node
        self.tail.next = new_node
        self.head = new_node
    def delete(self, data):
        if not self.head:
            return
        curr = self.head
        while True:
            if curr.data == data:
                if curr == self.head and curr == self.tail:  
                    self.head = None
                    self.tail = None
                elif curr == self.head:  
                    self.head = curr.next
                    self.head.prev = self.tail
                    self.tail.next = self.head
                elif curr == self.tail:  
                    self.tail = curr.prev
                    self.tail.next = self.head
                    self.head.prev = self.tail
                else:  
                    curr.prev.next = curr.next
                    curr.next.prev = curr.prev
                return
            curr = curr.next
            if curr == self.head:  
                break
此外,這個資料結構並不是固定的,可以依據需求增加功能,像是這裡的例子就都沒有很重要的 find。
謝謝~

參考資料
Thomas H. Cormen、Charles E. Leiserson、Ronald L. Rivest、Clifford Stein,《演算法導論》,碁峰資訊,2024 年
ChatGPT